#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
__title__ = ''
__author__ = 'HaiFeng'
__mtime__ = '2016/9/22'
"""

import _thread
import time
from time import sleep

from py_ctp.src.ctp_struct import *

from py_at.Enums import OrderStatus
from py_at.adapters.TradeAdapter import *
from py_at.at_struct import InstrumentField, TradingAccount, PositionField, OrderField
from py_at.switch import *
from py_ctp.src.trade import *


class CtpTrade(TradeAdapter):
    """"""

    def __init__(self):
        super().__init__()
        self._req = 0
        """防止重连时启动太多线程"""
        self.qryStart = False
        self.__dic_orderid_sysid = {}
        self.__posi = []
        self.t = Trade()

    # ------------------
    def __qry(self):
        """查询帐号相关信息"""
        self.qryStart = True
        """restart 模式 等待rtnorder 处理完毕再进行查询，否则会造成position混乱"""
        ord_cnt = 0
        while True:
            time.sleep(0.5)
            if len(self.DicOrderField) == ord_cnt:
                break
            ord_cnt = len(self.DicOrderField)
        self.t.ReqQryInstrument()
        time.sleep(1.1)
        while not self.Account or self.IsLogin:
            """查询持仓与权益"""
            # print('已经启用了自动查询')
            sleep(1.1)
            self.t.ReqQryInvestorPosition(self.BrokerID, self.Investor)
            sleep(1.1)
            self.t.ReqQryTradingAccount(self.BrokerID, self.Investor)

    def __OnFrontConnected(self):
        self.OnFrontConnected()

    def __OnFrontDisconnected(self, nReason):
        self.IsLogin = False
        self.OnFrontDisConnected(nReason)

    def __OnRspUserLogin(self, pRspUserLogin=CThostFtdcRspUserLoginField, pRspInfo=CThostFtdcRspInfoField,
                         nRequestID=int, bIsLast=bool):
        """登陆回报"""
        self.Investor = pRspUserLogin.getUserID()
        self.BrokerID = pRspUserLogin.getBrokerID()

        self.SessionID = pRspUserLogin.getSessionID()
        self.TradingDay = pRspUserLogin.getTradingDay()

        # 如果有错误  直接返回info
        if pRspInfo.getErrorID() != 0:
            info = InfoField()
            info.ErrorID = pRspInfo.getErrorID()
            info.ErrorMsg = pRspInfo.getErrorMsg()
            self.OnRspUserLogin(info)
        else:  # 如果没有错误了，直接确认账单了，并且直接开启实时查询了
            self.t.ReqSettlementInfoConfirm(self.BrokerID, self.Investor)
            if not self.qryStart:  # 防止重链启动太多线程进行查询
                time.sleep(0.5)
                """查询持仓与权益"""
                _thread.start_new_thread(self.__qry, ())  # 开启查询

    def __OnRspQryInstrument(self, pInstrument=CThostFtdcInstrumentField, pRspInfo=CThostFtdcRspInfoField,
                             nRequestID=int, bIsLast=bool):
        """合约回报"""
        inst = InstrumentField()
        inst.InstrumentID = pInstrument.getInstrumentID()
        inst.ProductID = pInstrument.getProductID()
        inst.ExchangeID = pInstrument.getExchangeID()
        inst.VolumeMultiple = pInstrument.getVolumeMultiple()
        inst.PriceTick = pInstrument.getPriceTick()
        inst.MaxOrderVolume = pInstrument.getMaxLimitOrderVolume()
        self.DicInstrument[inst.InstrumentID] = inst

    def __OnRspQryAccount(self, pTradingAccount=CThostFtdcTradingAccountField, pRspInfo=CThostFtdcRspInfoField,
                          nRequestID=int, bIsLast=bool):
        """账户权益查询回报"""
        if not self.Account:
            self.Account = TradingAccount()
        self.Account.AccountId = self.Investor
        self.Account.Available = pTradingAccount.getAvailable()
        self.Account.CloseProfit = pTradingAccount.getCloseProfit()
        self.Account.Commission = pTradingAccount.getCommission()
        self.Account.CurrMargin = pTradingAccount.getCurrMargin()
        self.Account.FrozenCash = pTradingAccount.getFrozenCash()
        self.Account.PositionProfit = pTradingAccount.getPositionProfit()
        self.Account.PreBalance = pTradingAccount.getPreBalance() + pTradingAccount.getDeposit() + pTradingAccount.getWithdraw()
        self.Account.Fund = self.Account.PreBalance + pTradingAccount.getCloseProfit() + pTradingAccount.getPositionProfit() - pTradingAccount.getCommission()
        self.Account.Risk = self.Account.CurrMargin / self.Account.Fund


        # 直到账户查询完毕之后自己封装一个返回值传给调用者
        if not self.IsLogin:
            self.IsLogin = True
            info = InfoField()
            info.ErrorID = 0
            info.ErrorMsg = '正确'
            self.OnRspUserLogin(info)
        else:
            _thread.start_new_thread(self.OnRspQryAccount, (self.Account,))

    # -----------------------------------------------------------------------------------------------------------------
    def __OnRspQryPosition(self, pInvestorPosition=CThostFtdcInvestorPositionField, pRspInfo=CThostFtdcRspInfoField,
                           nRequestID=int, bIsLast=bool):
        """持仓查询回报"""
        if not self.__posi:
            self.__posi = []

        if pInvestorPosition.getInstrumentID() != '':  # 偶尔出现NULL的数据导致数据转换错误
            self.__posi.append(pInvestorPosition)  # Struct(**f.__dict__)) #dict -> object

        if bIsLast:
            output_dict = {}
            # 对所有返回的持仓进行分组  rb1710 多   所有信息合并起来  参考网址http://blog.csdn.net/Nero_G/article/details/54574231
            for item in self.__posi:
                output_dict.setdefault('{0}_{1}'.format(item.getInstrumentID(), int(
                    DirectType.Buy if item.getPosiDirection() == PosiDirectionType.Long else DirectType.Sell)),
                                       []).append(item)
            for key, v in output_dict.items():
                pf = self.DicPositionField.get(key)
                if not pf:
                    pf = PositionField()
                    self.DicPositionField[key] = pf
                pf.AccountId =self.Investor
                pf.Position = 0
                pf.TdPosition = 0
                pf.YdPosition = 0
                pf.CloseProfit = 0
                pf.PositionProfit = 0
                pf.Commission = 0
                pf.Margin = 0
                pf.Price = 0
                cost = 0.0
                for g in v:
                    if not pf.InstrumentID:
                        pf.InstrumentID = g.getInstrumentID()
                        pf.Direction = DirectType.Buy if g.getPosiDirection() == PosiDirectionType.Long else DirectType.Sell
                    pf.Position += g.getPosition()
                    pf.TdPosition += g.getTodayPosition()
                    pf.YdPosition = pf.Position - pf.TdPosition
                    pf.CloseProfit += g.getCloseProfit()
                    pf.PositionProfit += g.getPositionProfit()
                    pf.Commission += g.getCommission()
                    pf.Margin += g.getUseMargin()
                    cost += g.OpenCost
                # pf.Position <= 0 ? 0 : (g.Sum(n => n.PositionCost) / DicInstrumentField[pf.InstrumentID].VolumeMultiple / pf.Position);

                vm = self.DicInstrument[pf.InstrumentID].VolumeMultiple
                pf.Price = 0 if pf.Position <= 0 else cost / vm / pf.Position
            self.__posi.clear()

            _thread.start_new_thread(self.OnRspQryPosition, (self.DicPositionField,))


            # ----------------------------------------------------------------------------------

    def __OnRtnOrder(self, pOrder=CThostFtdcOrderField):
        """报单回报函数 私有流  不管是报的买卖单或者是撤单 订单变化都经过这里"""
        id = '{0}|{1}|{2}'.format(pOrder.getSessionID(), pOrder.getFrontID(), pOrder.getOrderRef())
        # of = OrderField()
        of = self.DicOrderField.get(id)
        if not of:  # 如果不存在 那么填充到self.DicOrderField  并且触发OnRtnOrder
            of = OrderField()
            l = int(pOrder.getOrderRef())
            of.AccountId=self.Investor
            of.Custom = l % 1000000
            of.InstrumentID = pOrder.getInstrumentID()
            of.InsertTime = pOrder.getInsertTime()
            of.Direction = DirectType.Buy if DirectionType(
                pOrder.getDirection()) == DirectionType.Buy else DirectType.Sell
            ot = OffsetFlagType(ord(pOrder.getCombOffsetFlag()[0]))
            of.Offset = OffsetType.Open if ot == OffsetFlagType.Open else (
                OffsetType.CloseToday if ot == OffsetFlagType.CloseToday else OffsetType.Close)
            of.Status = OrderStatus.Normal
            of.StatusMsg = pOrder.getStatusMsg()
            of.IsLocal = pOrder.getSessionID() == self.SessionID
            of.LimitPrice = pOrder.getLimitPrice()
            of.OrderID = id
            of.Volume = pOrder.getVolumeTotalOriginal()
            of.VolumeLeft = of.Volume
            self.DicOrderField[id] = of
            self.OnRtnOrder(of)
        # _thread.start_new_thread(self.OnRtnOrder, (of,))  # call client OnRtnOrder event
        elif pOrder.getOrderStatus() == OrderStatusType.Canceled:  # 报单被取消
            of.Status = OrderStatus.Canceled
            of.StatusMsg = pOrder.getStatusMsg()

            if of.StatusMsg.find('被拒绝') >= 0:  # 如果存在错误  那么直接返回
                info = InfoField()
                info.ErrorID = -1
                info.ErrorMsg = of.StatusMsg
                self.OnRtnErrOrder(of, info)
            else:
                self.OnRtnCancel(of)  # 这个应该是撤单成功吧 原因1 上面==报单被取消 又没有错误提示
        else:
            if pOrder.getOrderSysID():
                of.SysID = pOrder.getOrderSysID()
                self.__dic_orderid_sysid[pOrder.getOrderSysID()] = id  # 记录sysid与orderid关联,方便Trade时查找处理
            _thread.start_new_thread(self.OnRtnOrder, (of,))

    def __OnRtnTrade(self, f):
        """成交回报"""
        tf = TradeField()
        tf.AccountId = self.Investor
        tf.Direction = f.getDirection()
        tf.ExchangeID = f.getExchangeID()
        tf.InstrumentID = f.getInstrumentID()
        tf.Offset = OffsetType.Open if f.getOffsetFlag() == OffsetFlagType.Open else OffsetType.Close if f.getOffsetFlag() == OffsetFlagType.Close else OffsetType.CloseToday
        tf.Price = f.getPrice()
        tf.SysID = f.getOrderSysID()
        tf.TradeID = f.getTradeID()
        tf.TradeTime = f.getTradeTime()
        tf.TradingDay = f.getTradingDay()
        tf.Volume = f.getVolume()

        self.DicTradeField[tf.TradeID] = tf

        id = self.__dic_orderid_sysid[tf.SysID]
        of = self.DicOrderField[id]
        tf.Custom = of.Custom   
        tf.OrderID = id  # tradeid 与 orderid 关联

        of.TradeTime = tf.TradeTime
        # of.AvgPrice = (of.AvgPrice * (of.Volume - of.VolumeLeft) + pTrade.Price * pTrade.Volume) / (of.Volume - of.VolumeLeft + pTrade.Volume);
        of.AvgPrice = (of.AvgPrice * (of.Volume - of.VolumeLeft) + tf.Price * tf.Volume) / (
            of.Volume - of.VolumeLeft + tf.Volume)
        of.TradeVolume = tf.Volume
        of.VolumeLeft -= tf.Volume
        if of.VolumeLeft == 0:
            of.Status = OrderStatus.Filled
            of.StatusMsg = '全部成交'
        else:
            of.Status = OrderStatus.Partial
            of.StatusMsg = '部分成交'

        # 更新持仓 *****

        if tf.Offset == OffsetType.Open:
            key = '{0}_{1}'.format(tf.InstrumentID,
                                   int(DirectType.Buy if tf.Direction == DirectionType.Buy else DirectType.Sell))

            pf = self.DicPositionField.get(key)
            if not pf:
                pf = PositionField()
                self.DicPositionField[key] = pf
            pf.AccountId = self.Investor
            pf.InstrumentID = tf.InstrumentID
            # pf.Direction = tf.Direction.
            pf.Direction = DirectType.Buy if tf.Direction == DirectionType.Buy else DirectType.Sell

            if (pf.Position == 0 and pf.Price == 0):
                pf.Price = (tf.Price * tf.Volume) / (tf.Volume)
            else:
                if (pf.Position != -1):  # 这个地方有时候会报错 所以加上这条 但是不太明白什么意思 理论上这个地方是否可以都删除不要了
                    pf.Price = (pf.Price * pf.Position + tf.Price * tf.Volume) / (pf.Position + tf.Volume)
            pf.TdPosition += tf.Volume
            pf.Position += tf.Volume
        else:
            # key = '{0}_{1}'.format(tf.InstrumentID,
            #                         int(DirectType.Sell if tf.Direction == DirectType.Buy else DirectType.Buy))

            key = '{0}_{1}'.format(tf.InstrumentID,
                                   int(DirectType.Sell if tf.Direction == DirectionType.Buy else DirectType.Buy))
            pf = self.DicPositionField.get(key)
            if pf:  # 有可能出现无持仓的情况
                if tf.Offset == OffsetType.CloseToday:
                    pf.TdPosition -= tf.Volume
                else:
                    tdclose = min(pf.TdPosition, tf.Volume)
                    if pf.TdPosition > 0:
                        pf.TdPosition -= tdclose
                    pf.YdPosition -= max(0, tf.Volume - tdclose)
                pf.Position -= tf.Volume

        # for te in self.DicPositionField:
        #     te=self.DicPositionField[te]
        #     print('合约{0},方向{1},价格{2},持仓{3},昨仓{4},今仓{5},平仓利润{6},持仓利润{7},手续费{8},{9}'.format(te.InstrumentID,te.Direction,te.Price,te.Position,te.YdPosition,te.TdPosition,te.CloseProfit,te.PositionProfit,te.Commission,te.Margin))
        # 注解：这里使用线程的原因是不是因为可能交易回报比较多，如果使用普通函数等待返回然后执行下一条害怕阻塞，所以直接扔出去新线程让他去执行？
        _thread.start_new_thread(self.__onRtn, (of, tf))

    def __onRtn(self, of, tf):
        self.OnRtnOrder(of)
        self.OnRtnTrade(tf)

    # OnRspOrderInsert
    def __OnRspOrder(self, pInputOrder=CThostFtdcInputOrderField, pRspInfo=CThostFtdcRspInfoField, nRequestID=int,
                     bIsLast=bool):
        """撤单录入回调，CTP表层判断即报单参数基本的校验，如校验不通过，会收到回调信息。 对话流 """
        info = InfoField()
        info.ErrorID = pRspInfo.getErrorID()
        info.ErrorMsg = pRspInfo.getErrorMsg()

        id = '{0}|{1}|{2}'.format(self.SessionID, '0', pInputOrder.getOrderRef())
        of = self.DicOrderField.get(id)
        if not of:
            of = OrderField()
            of.AccountId = self.Investor
            l = int(pInputOrder.getOrderRef())

            of.Custom = l % 1000000
            of.InstrumentID = pInputOrder.getInstrumentID()
            of.InsertTime = time.strftime('%Y%M%d %H:%M:%S', time.localtime())
            # 对direction需特别处理（具体见ctp_struct）
            of.Direction = DirectType.Buy if DirectionType(
                pInputOrder.getDirection()) == DirectionType.Buy else DirectType.Sell
            ot = OffsetFlagType(ord(pInputOrder.getCombOffsetFlag()[0]))
            of.Offset = OffsetType.Open if ot == OffsetFlagType.Open else (
                OffsetType.CloseToday if ot == OffsetFlagType.CloseToday else OffsetType.Close)
            # of.Status = OrderStatus.Normal
            # of.StatusMsg = f.getStatusMsg()
            of.IsLocal = True
            of.LimitPrice = pInputOrder.getLimitPrice()
            of.OrderID = id
            of.Volume = pInputOrder.getVolumeTotalOriginal()
            of.VolumeLeft = of.Volume
            self.DicOrderField[id] = of
        of.Status = OrderStatus.Error
        of.StatusMsg = '{0}:{1}'.format(info.ErrorID, info.ErrorMsg)
        _thread.start_new_thread(self.OnRtnErrOrder, (of, info))

    def __OnErrOrder(self, pInputOrder=CThostFtdcInputOrderField, pRspInfo=CThostFtdcRspInfoField):
        """报单录入错误回报  私有回报"""
        id = '{0}|{1}|{2}'.format(self.SessionID, '0', pInputOrder.getOrderRef())
        of = self.DicOrderField.get(id)

        info = InfoField()
        info.ErrorID = pRspInfo.getErrorID()
        info.ErrorMsg = pRspInfo.getErrorMsg()

        if of and of.IsLocal:
            of.Status = OrderStatus.Error
            of.StatusMsg = '{0}:{1}'.format(pRspInfo.getErrorID(), pRspInfo.getErrorMsg())
            _thread.start_new_thread(self.OnRtnErrOrder, (of, info))

    def __OnRtnInstrumentStatus(self, pInstrumentStatus=CThostFtdcInstrumentStatusField):
        self.DicInstrumentStatus[pInstrumentStatus.getInstrumentID()] = pInstrumentStatus.getInstrumentStatus()
        _thread.start_new_thread(self.OnRtnInstrumentStatus,
                                 (pInstrumentStatus.getInstrumentID(), pInstrumentStatus.getInstrumentStatus()))

    def __OnRspSettlementInfoConfirm(self, pSettlementInfoConfirm=CThostFtdcSettlementInfoConfirmField,
                                     pRspInfo=CThostFtdcRspInfoField, nRequestID=int, bIsLast=bool):
        pass

    def ReqConnect(self, pAddress=''):
        self.t.CreateApi()
        spi = self.t.CreateSpi()
        self.t.RegisterSpi(spi)

        self.t.OnFrontConnected = self.__OnFrontConnected
        self.t.OnRspUserLogin = self.__OnRspUserLogin
        self.t.OnFrontDisconnected = self.__OnFrontDisconnected
        self.t.OnRspSettlementInfoConfirm = self.__OnRspSettlementInfoConfirm
        self.t.OnRtnOrder = self.__OnRtnOrder  # 报单回报（订单状态发生变化 排队 部分成交等）                  私有流
        self.t.OnRtnTrade = self.__OnRtnTrade  # 成交回报（订单撮合成功回报）  私有流
        self.t.OnRspOrderInsert = self.__OnRspOrder  # 报单录入回报（报单录入回调，CTP表层判断即报单参数基本的校验(意思就是如果报单错误 就不送到ctp了 直接返回错误信息了)，如校验不通过，会收到回调信息。）  对话流
        self.t.OnErrRtnOrderInsert = self.__OnErrOrder  # 报单录入错误回报(如果报单发生错误会发送这个)  私有流

        self.t.OnRtnInstrumentStatus = self.__OnRtnInstrumentStatus

        self.t.OnRspQryInstrument = self.__OnRspQryInstrument  # 查询流  合约查询
        self.t.OnRspQryTradingAccount = self.__OnRspQryAccount
        self.t.OnRspQryInvestorPosition = self.__OnRspQryPosition  # 查询流   仓位查询

        self.t.RegCB()

        self.t.RegisterFront(pAddress)
        self.t.Init()

    def ReqUserLogin(self, user, pwd, broker):
        self.t.ReqUserLogin(
            BrokerID=broker,
            UserID=user,
            Password=pwd
        )

    def ReqOrderInsert(self, pInstrument='', pDirection=DirectType, pOffset=OffsetType, pPrice=0.0, pVolume=1,
                       pType=OrderType.Limit, pCustom=0):
        """报单"""
        OrderPriceType = OrderPriceTypeType.AnyPrice
        TimeCondition = TimeConditionType.IOC
        LimitPrice = 0.0
        VolumeCondition = VolumeConditionType.AV

        for case in switch(pType):
            if case(OrderType.Market):  # 市价
                OrderPriceType = OrderPriceTypeType.AnyPrice
                TimeCondition = TimeConditionType.IOC
                LimitPrice = 0.0
                VolumeCondition = VolumeConditionType.AV
                break
            if case(OrderType.Limit):  # 限价
                OrderPriceType = OrderPriceTypeType.LimitPrice
                TimeCondition = TimeConditionType.GFD
                LimitPrice = pPrice
                VolumeCondition = VolumeConditionType.AV
                break
            if case(OrderType.FAK):  # FAK
                OrderPriceType = OrderPriceTypeType.LimitPrice
                TimeCondition = TimeConditionType.IOC
                LimitPrice = pPrice
                VolumeCondition = VolumeConditionType.AV
                break
            if case(OrderType.FOK):  # FOK
                OrderPriceType = OrderPriceTypeType.LimitPrice
                TimeCondition = TimeConditionType.IOC
                LimitPrice = pPrice
                VolumeCondition = VolumeConditionType.CV  # 全部数量
                break

        self._req += 1
        self.t.ReqOrderInsert(
            BrokerID=self.BrokerID,
            InvestorID=self.Investor,
            InstrumentID=pInstrument,
            OrderRef="%06d%06d" % (self._req, pCustom % 1000000),
            UserID=self.Investor,
            # 此处ctp_enum与at_struct名称冲突
            # Direction=DirectionType.Buy if pDirection == DirectType.Buy else DirectionType.Sell,
            Direction=DirectionType.Buy if pDirection == DirectionType.Buy else DirectionType.Sell,
            CombOffsetFlag=chr(OffsetFlagType.Open if pOffset == OffsetType.Open else (
                OffsetFlagType.CloseToday if pOffset == OffsetType.CloseToday else OffsetFlagType.Close)),
            CombHedgeFlag=HedgeFlagType.Speculation.__char__(),
            IsAutoSuspend=0,
            ForceCloseReason=ForceCloseReasonType.NotForceClose,
            IsSwapOrder=0,
            ContingentCondition=ContingentConditionType.Immediately,
            VolumeCondition=VolumeCondition,
            MinVolume=1,
            VolumeTotalOriginal=pVolume,
            OrderPriceType=OrderPriceType,
            TimeCondition=TimeCondition,
            LimitPrice=LimitPrice,
        )

    def ReqOrderAction(self, OrderID=''):
        """撤单"""
        of = self.DicOrderField[OrderID]

        if not of:
            return -1
        else:
            pOrderId = of.OrderID
            # f = CThostFtdcInputOrderActionField()
            # f.ActionFlag = ActionFlagType.Delete
            # f.BrokerID = self.BrokerID
            # f.InstrumentID = of.InstrumentID
            # f.InvestorID = of.InvestorID
            # SessionID = int(pOrderId.Split('|')[0])
            # FrontID = int(pOrderId.Split('|')[1])
            # OrderRef = pOrderId.Split('|')[2]
            return self.t.ReqOrderAction(
                self.BrokerID,
                self.Investor,
                OrderRef=pOrderId.split('|')[2],
                FrontID=int(pOrderId.split('|')[1]),
                SessionID=int(pOrderId.split('|')[0]),
                InstrumentID=of.InstrumentID,
                ActionFlag=ActionFlagType.Delete
            )

    def Release(self):
        self.t.RegisterSpi(None)
        self.t.Release()

    # ------------------
    def OnFrontConnected(self):
        """"""
        pass

    def OnFrontDisConnected(self, error=0):
        """"""
        pass

    def OnRspUserLogin(self, info=InfoField):
        """"""
        pass

    def OnRtnOrder(self, f=OrderField):
        """"""
        pass

    def OnRtnTrade(self, f=TradeField):
        """"""
        pass

    def OnRtnCancel(self, f=OrderField):
        """"""
        pass

    def OnRtnErrOrder(self, f=OrderField, info=InfoField):
        """"""
        pass
        # print("返回单子错误 OnRtnErrOrder")
        # print(f)
        # print(info)

    def OnRtnInstrumentStatus(self, inst, status):
        pass

    def OnRspQryPosition(self, PositionFilld):
        """持仓自动查询
        :param PositionFilld:
        :return:
        """
        pass

    def OnRspQryAccount(self, Account):
        """账户资金回报查询"""
        pass
